#include <stddef.h>     /* NULL */
#include <assert.h>     /* assert() */
#include <stdlib.h>     /* malloc(), free() ... */
#include <sys/types.h>  /* regmatch_t, regex_t, ... */
#include <regex.h>      /* RegEx functions */
#include <stdio.h>
#include <string.h>
#include "header/checkFile.h"
#include "header/headers.h"

#define LENGTH(x) (sizeof(x) / sizeof(*(x)))
#define PRIME 13

/* 
 * ===  FUNCTION  =============================================================
 *         Name:  checkFile
 *  Description:  Runs the implemented functions on the filecontent passed.
 *  Parameters:   fileContent: Content of the opened file
 *  Return:       If an error occured -1, else 0
 * ============================================================================
 */
int checkFile(char *fileContent) {
    assert(fileContent != NULL);
    
    int i, includeCount;
    char **includes;
    
    includes = (char*)malloc(INCLUDE_BUFFER * sizeof(char*));
    includeCount = findIncludes(fileContent, &includes);
    if(includeCount == -1) {
        printError(4);
        return 5;
    } 

    checkWords(fileContent,includes,includeCount);

    for(i = 0; i < includeCount; i++) {
        free(includes[i]);
    }
    free(includes);
    
    return 0;
}

static int checkWords(const char *fileContent, const char **includes, int includeCount) {

    int regReturn, length, includeNumber = -1, w, i, begin, end;
    char* word;
    regmatch_t match;
    regex_t regex;
    regoff_t last_match = 0;

    regReturn = regcomp(&regex, WORD_PATTERN, REG_EXTENDED);
    if(regReturn) {
        return -1; /* RegEx compilation error */
    }

    while(((regReturn = regexec(&regex, (fileContent+last_match), 1, &match, 0)) == 0)) {
        ++includeNumber;
        w = 0; /* Reset "w" for the next array */

        begin = ((int)match.rm_so + last_match);    /* Start of matched string */
        end = ((int)match.rm_eo + last_match);      /* End of matched string */
        length = end-begin;

        word = mallocError(malloc((length + 1) * sizeof(char)));

        for(i = begin; i < end; i++) {
            word[w] = fileContent[i];
            ++w;
        }
        word[w] = '\0';  /* Set string-end @ last position */

        if(checkDefinition(word, includes, includeCount) == 1) {
            printf("MATCHED: %s \n", word);
        } else {
            printf("NOT MATCHED: %s \n", word);
        }

        last_match += match.rm_eo + 1;
    }

    return 0;
}

static int checkDefinition(const char *word, const char **includes, int includeCount) {
    int  i = 0, hash = 0;
    FILE *keywordFile;
    char buf[MAX_LINE_LENGTH];
    char *tmp;
    header *head;

    keywordFile = fopen(KEYWORD_FILE, "r");
    if(keywordFile == NULL) {
        printError(0);
        return -1;
    }

    hash = getHash(word);

    /*
    while(fgets(buf, MAX_LINE_LENGTH, keywordFile) != NULL) {
        ++i;
        if(i == hash) {
            break;
        }
        buf[strcspn(buf, "\n")] = '\0';
        if(buf[0] == '\0') {
            continue;
        }
    }*/
    for( i = 0; i < includeCount; i++) {
        
        head = loadHeader(includes[i], 0);

        tmp = strtok(head->keywords, KEYWORD_DELIMITER);
        while(tmp != NULL) {
            if(strcmp(word, tmp) == 0) {
                return 1;
            }
            tmp = strtok(NULL, KEYWORD_DELIMITER);
        }
        printf("%s (%d) : %s (Line: %d)\n", word, hash, tmp, i);
    }

     /*
    for(i = 0; i < LENGTH(keywords[hash]); i++) {
        if(strcmp(word,keywords[i]) == 0) {
            return 1;
        }
    }*/
    return 0;
}

static int getHash(const char *word) {
    int i, adr = 0;
    for(i = 0; i < strlen(word); i++) {
        adr = 31*adr + word[i];
    }

    if(adr < 0) {
        adr = -adr;
    }

    return adr % PRIME;
}

/*
 * ===  FUNCTION  =============================================================
 *         Name:  findIncludes
 *  Description:  Scans the passed filecontent for includes via RegEx.
 *                All includes are stored in "includes".
 *  Parameters:   fileContent:  Content of the opened file
 *                includes:     Adress of the array where the pointers to matched
 *                              strings are stored
 *  Return:       Number of includes found, -1 on error
 * ============================================================================
 */
static int findIncludes(const char *fileContent, char ***includes) {
    assert(*includes != NULL);

    int flag;
    int regReturn, begin, end, i, w, length, includeNumber = -1;
    int newSize = ((INCLUDE_BUFFER * 2) * sizeof(char*));

    regmatch_t match;
    regex_t regex;
    regoff_t last_match = 0;

    /* Compile RegEx and check for success */
    regReturn = regcomp(&regex, SYS_INCLUDE_PATTERN, REG_EXTENDED);
    if(regReturn) {
        return -1; /* RegEx compilation error */
    }

     /* Traverse file content and match includes with RegEx */
    while(((regReturn = regexec(&regex, (fileContent+last_match), 1, &match, 0)) == 0)) {
        ++includeNumber;
        flag = 0;
        w = 0; /* Reset "w" for the next array */

        if(includeNumber == INCLUDE_BUFFER) {
            *includes = (char*) mallocError(realloc(*includes, newSize));
        }            
        
        begin = ((int)match.rm_so + last_match);    /* Start of matched string */
        end = ((int)match.rm_eo + last_match);      /* End of matched string */
        length = end-begin;

        (*includes)[includeNumber] = mallocError(malloc((length + 1) * sizeof(char)));
        
        for(i = begin; i < end; i++) {
            if(fileContent[i+1] == '>') {
                flag = 0;
            }
            else if(flag == 1 || fileContent[i] == '<') {
                flag = 1;
                (*includes)[includeNumber][w] = fileContent[i+1];
                ++w;
            }
        }
        (*includes)[includeNumber][w] = '\0';  /* Set string-end @ last position */

        last_match += match.rm_so + 1;
    }

    if(regReturn == REG_NOMATCH && includeNumber <= 0) {
        includeNumber = 0;
    }

    regfree(&regex);
    return includeNumber;
}